/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.treepoke.util.ws;

import com.treepoke.util.dto.WebServiceDTO;
import com.treepoke.util.exception.BusinessException;
import com.treepoke.util.logging.CRCCLogger;
import com.treepoke.util.properties.TPConstant;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import javax.xml.bind.JAXB;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Node;

/**
 * Clase con metodos para la construccion de mensajes SOAP y manejo de las
 * respuestas SOAP
 */
public class SOAPRequestUtil {

    private static final String encoding = "UTF-8";
    private static CRCCLogger logger = CRCCLogger.getLogger(TPConstant.getNOMBRE_APLICACION());

    /**
     * Construir el mensaje estandar de request SOAP.
     *
     * @param operationName Nombre de la operacion
     * @param nameSpace Espacio de nombres asociado a la operacion.
     * @param parameters Parametros a usar en la operacion
     * @return
     */
    public static String buildMessageSOAP(WebServiceDTO webServiceDTO, Object[] parameters) {
        StringBuilder payload = new StringBuilder();
        if (parameters != null && parameters.length > 0) {
            for (Object o : parameters) {
                DOMResult result = new DOMResult();
                JAXB.marshal(o, result);
                payload.append(nodeToString(result.getNode(), true));
            }
        }
        StringBuilder msg = new StringBuilder();
        msg.append("<ns:").append(webServiceDTO.getNombreOperacion()).append(" xmlns:ns=\"").
                append(webServiceDTO.getNamespace()).append("\">").append(payload).append("</ns:").
                append(webServiceDTO.getNombreOperacion()).append(">");

        return msg.toString();
    }

    /**
     * Convierte un Nodo y toda su estructura a una representacion en cadena.
     *
     * @param node Nodo base a transformar
     * @param booleano, indica si se debe omitir la declaracion xml.
     * @return Representacion en un String de la estructura XML representada por
     * el nodo.
     */
    public static String nodeToString(Node node, boolean omitXMLDeclaration) {
        ByteArrayOutputStream ous = null;
        try {
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();

            transformer.setOutputProperty("omit-xml-declaration", omitXMLDeclaration ? "yes" : "no");

            ous = new ByteArrayOutputStream();
            StreamResult result = new StreamResult(ous);
            DOMSource sourceContent = new DOMSource(node);
            transformer.transform(sourceContent, result);
            return ous.toString(encoding);

        } catch (Exception e) {
            logger.errorMessage("Error al construir el nodo" + e, e);
        } finally {
            if (ous != null) {
                try {
                    ous.close();
                } catch (IOException e) {
                    logger.errorMessage("Error al cerrar el archivo" + e, e);
                }
            }
        }
        return null;
    }

    /**
     * Obtiene informacion a partir de un nodo XML. Es posible que el mensaje
     * SOAP obtenido refleje una excepcion de BusinessException, si es el caso
     * el metodo lanza la excepcion con el mensaje de error que viene indicado.
     * Se debe especificar que tipo de dato se esta esperando, que excepcion
     * puede lanzar y que tipo de mensaje de error se puede usar. El metodo
     * puede retornar un objeto o una lista de objetos dependiendo de la
     * respuesta.
     *
     * @param node Nodo con la respuesta SOAP
     * @param dataType Tipo de dato a retornar
     * @param exceptionType Tipo de excepcion posible
     * @param errorTypeType Tipo de mensaje de error posible.
     * @return Object, puede ser un objeto del tipo especificado o una lista de
     * objetos.
     * @throws U Excepcion posible a lanzar.
     */
    public static <T, U extends BusinessException, V> Object getDataFromNode(
            Node node, Class<T> dataType, Class<U> exceptionType,
            Class<V> errorTypeType, boolean collection) throws U {

        if (node.getFirstChild() != null && node.getFirstChild().getLocalName().equals("response")) {
            int size = node.getChildNodes().getLength();

            if (size == 1 && !collection) {
                T data = getObjectFromNode(node, dataType);
                return data;
            } else if ((size == 1 && collection) || size > 1) {
                Collection<T> dataList = getObjectsFromNode(node, dataType);
                return dataList;
            } else {
                return null;
            }
        } else if (node.getLocalName().equals(exceptionType.getSimpleName())) {
            throw SOAPRequestUtil.getExceptionFromNode(node, exceptionType, errorTypeType);
        }
        return null;
    }

    /**
     * Obtiene un objeto a partir de un nodo XML
     *
     * @param <T>
     * @param node Nodo XML
     * @param clazz Clase del objeto a devolver
     * @return objeto
     */
    public static <T> T getObjectFromNode(Node node, Class<T> clazz) {
        Node responseNode = node.getFirstChild();
        T item = deSerializeUsingJAXB(responseNode, clazz);
        return item;
    }

    /**
     * Obtiene una lista de objetos del mismo tipo a partir de un nodo XML
     *
     * @param <T>
     * @param node Nodo XML
     * @param clazz Clase del objeto a devolver
     * @return Lista de objetos.
     */
    public static <T> Collection<T> getObjectsFromNode(Node node, Class<T> clazz) {
        Collection<T> lista = new ArrayList<T>();

        Node responseNode = node.getFirstChild();
        do {
            T item = deSerializeUsingJAXB(responseNode, clazz);
            lista.add(item);
            responseNode = responseNode.getNextSibling();
        } while (responseNode != null);

        return lista;
    }

    /**
     * Permite obtener una excepcion en un nodo XML
     *
     * @param <T> Tipo de la excepcion
     * @param <U> Tipo de la enumeracion del mensaje de error.
     * @param node Nodo Xml con la informacion
     * @param exceptionType Tipo de Excepcion
     * @param errorTypeType Tipo de Mensaje de error.
     * @return Excepcion.
     */
    public static <T extends BusinessException, U> T getExceptionFromNode(
            Node node, Class<T> exceptionType, Class<U> errorTypeType) {

        String message = null;
        U tipoError = null;
        Node responseNode = node.getFirstChild();

        do {
            if (responseNode.getLocalName().equals("message")) {
                message = deSerializeUsingJAXB(responseNode, String.class);
            } else if (responseNode.getLocalName().equals("tipoError")) {
                tipoError = deSerializeUsingJAXB(responseNode, errorTypeType);
            }
            responseNode = responseNode.getNextSibling();
        } while (responseNode != null);

        Class<? extends Object> partypes[] = new Class[3];
        partypes[0] = String.class;
        partypes[1] = Throwable.class;
        partypes[2] = errorTypeType;

        Constructor<T> constructor = null;
        try {
            constructor = exceptionType.getConstructor(partypes);

            Object arglist[] = new Object[3];
            arglist[0] = message;
            arglist[1] = null;
            arglist[2] = tipoError;
            T retobj = constructor.newInstance(arglist);
            return retobj;
        } catch (Exception e) {
            throw new RuntimeException("Couldn't get Exception from XML Node. " + e);
        }
    }

    /**
     * Obtiene un Java Bean del tipo indicado usando una cadena XML.
     *
     * @param <T> Tipo de Objeto a retornar
     * @param xml Cadena con representacion Xml del objeto.
     * @param clazz Tipo de Objeto a retornar
     * @return Objeto
     */
    public static <T> T deSerializeUsingJAXB(String xml, Class<T> clazz) {
        return JAXB.unmarshal(new java.io.StringReader(xml), clazz);
    }

    /**
     * Obtiene un Java Bean del tipo indicado usando un Nodo XML (W3C).
     *
     * @param <T> Tipo de Objeto a retornar
     * @param node Nodo xml con la informacion serializada del objeto.
     * @param clazz Tipo de Objeto a retornar
     * @return Objeto
     */
    public static <T> T deSerializeUsingJAXB(Node node, Class<T> clazz) {
        return JAXB.unmarshal(new DOMSource(node), clazz);
    }
}
